<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>数独解题工具 - 多解版 chalide.cn</title>
<meta name="author" content="yujianyue, 15058593138@qq.com">

    <style>
        :root {
            --primary-color: #4a6fa5;
            --secondary-color: #6b8cae;
            --success-color: #4caf50;
            --info-color: #2196f3;
            --warning-color: #ff9800;
            --danger-color: #f44336;
            --light-gray: #f5f5f5;
            --medium-gray: #e0e0e0;
            --dark-gray: #757575;
            --text-color: #333;
            --border-radius: 6px;
            --box-shadow: 0 4px 8px rgba(0,0,0,0.1);
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            color: var(--text-color);
            background-color: #f9f9f9;
            max-width: 1200px;
            margin: 0 auto;
            padding: 10px;
        }
        
        h1 {
            text-align: center;
            color: var(--primary-color);
            margin-bottom: 10px;
            font-weight: 300;
            font-size: 2.5rem;
        }
        
        .container {
            background-color: white;
            border-radius: var(--border-radius);
            box-shadow: var(--box-shadow);
            padding: 10px;
            margin-bottom: 10px;
        }
        
        .input-area {
            margin-bottom: 10px;
        }
        
        .input-group {
            display: flex;
            flex-direction: column;
            margin-bottom: 10px;
        }
        
        label {
            margin-bottom: 2px;
            font-weight: 500;
            color: var(--primary-color);
        }
        
        #sudoku-input {
            width: 100%;
            height: 30px;
            padding: 12px 0;
            font-size: 16px;
            border: 1px solid var(--medium-gray);
            border-radius: var(--border-radius);
            resize: vertical;
            transition: border 0.3s;
        }
        
        #sudoku-input:focus {
            outline: none;
            border-color: var(--primary-color);
            box-shadow: 0 0 0 2px rgba(74, 111, 165, 0.2);
        }
        
        .controls {
            display: flex;
            flex-wrap: wrap;
            gap: 15px;
            margin-top: 10px;
        }
        
        .control-group {
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        button {
            padding: 10px;
            background-color: var(--primary-color);
            color: white;
            border: none;
            border-radius: var(--border-radius);
            cursor: pointer;
            font-size: 16px;
            transition: all 0.3s;
            flex: 1;
            min-width: 120px;
        }
        
        button:hover {
            background-color: var(--secondary-color);
            transform: translateY(-2px);
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
        }
        
        button:active {
            transform: translateY(0);
        }
        
        button.secondary {
            background-color: var(--medium-gray);
            color: var(--text-color);
        }
        
        button.secondary:hover {
            background-color: #d0d0d0;
        }
        
        input[type="number"] {
            width: 80px;
            padding: 8px 12px;
            border: 1px solid var(--medium-gray);
            border-radius: var(--border-radius);
        }
        
        .message {
            padding: 12px 15px;
            border-radius: var(--border-radius);
            margin-top: 15px;
            display: none;
        }
        
        .error {
            background-color: #ffebee;
            border-left: 4px solid var(--danger-color);
            color: var(--danger-color);
            display: block;
        }
        
        .success {
            background-color: #e8f5e9;
            border-left: 4px solid var(--success-color);
            color: var(--success-color);
            display: block;
        }
        
        .stats {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            margin: 5px 0;
        }
        
        .stat-card {
            background-color: white;
            border-radius: var(--border-radius);
            box-shadow: var(--box-shadow);
            padding: 5px;
            text-align: center;
        }
        
        .stat-title {
            font-size: 14px;
            color: var(--dark-gray);
            margin-bottom: 5px;
        }
        
        .stat-value {
            font-size: 24px;
            font-weight: 600;
            color: var(--primary-color);
        }
        
        .time-value {
            color: var(--info-color);
        }
        
        .solutions-container {
            margin-top: 10px;
        }
        
        .solution-title {
            font-size: 0.9rem;
            color: var(--primary-color);
            margin: 10px 0 10px;
            padding-bottom: 5px;
            border-bottom: 1px solid var(--medium-gray);
            display: flex;
        }
        
        .copy-btn {
            padding: 5px;
            max-width: 68px
            background-color: var(--info-color);
            color: white;
            border: none;
            border-radius: var(--border-radius);
            cursor: pointer;
            font-size: 0.9rem;
            transition: all 0.2s;
        }
        
        .copy-btn:hover {
            background-color: #0d8aee;
        }
        
        .copy-btn.copied {
            background-color: var(--success-color);
        }
        
        .sudoku-grids {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
            gap: 30px;
            margin-top: 20px;
        }
        
        .sudoku-wrapper {
            background-color: white;
            border-radius: var(--border-radius);
            box-shadow: var(--box-shadow);
            padding: 20px;
            position: relative;
        }
        
        .sudoku-grid {
            display: grid;
            grid-template-columns: repeat(9, 1fr);
            grid-template-rows: repeat(9, 1fr);
            gap: 1px;
            background-color: var(--medium-gray);
            border: 2px solid var(--medium-gray);
            margin: 0 auto;
            max-width: 400px;
        }
        
        .cell {
            aspect-ratio: 1;
            background-color: white;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 1.2rem;
            font-weight: bold;
            position: relative;
            cursor: pointer;
            transition: background-color 0.2s;
        }
        
        .cell:hover {
            background-color: #f0f8ff;
        }
        
        .cell.original {
            color: var(--dark-gray);
            background-color: var(--light-gray);
        }
        
        .cell.solved {
            color: var(--info-color);
        }
        
        .cell.enumerated {
            color: var(--success-color);
        }
        
        .cell-count {
            position: absolute;
            top: 3px;
            right: 3px;
            font-size: 0.7rem;
            color: var(--dark-gray);
        }
        
        .cell-hint {
            display: none;
            position: absolute;
            top: 100%;
            left: 0;
            z-index: 100;
            background-color: white;
            border: 1px solid var(--medium-gray);
            padding: 8px;
            font-size: 0.9rem;
            width: 180px;
            box-shadow: var(--box-shadow);
            border-radius: var(--border-radius);
        }
        
        .cell:hover .cell-hint {
            display: block;
        }
        
        .thick-border-right {
            border-right: 2px solid var(--text-color);
        }
        
        .thick-border-bottom {
            border-bottom: 2px solid var(--text-color);
        }
        
        .status {
            padding: 15px;
            border-radius: var(--border-radius);
            margin: 20px 0;
        }
        
        .progress {
            background-color: #e3f2fd;
            border-left: 4px solid var(--info-color);
        }
        
        .completed {
            background-color: #e8f5e9;
            border-left: 4px solid var(--success-color);
        }
        
        .loading {
            display: inline-block;
            width: 20px;
            height: 20px;
            border: 3px solid rgba(74, 111, 165, 0.3);
            border-radius: 50%;
            border-top-color: var(--primary-color);
            animation: spin 1s ease-in-out infinite;
            margin-right: 10px;
            vertical-align: middle;
        }
        
        @keyframes spin {
            to { transform: rotate(360deg); }
        }
        
        @media (max-width: 768px) {
            .controls {
                flex-direction: column;
            }
            
            button {
                width: 100%;
            }
            
            .sudoku-grids {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
    <h1>数独解题工具</h1>
    
    <div class="container">
        <div class="input-area">
            <div class="input-group">
                <label for="sudoku-input">请输入81位数字(0表示空格)：</label>
                <textarea id="sudoku-input" placeholder="81位数字">530070000600195000098000060800060003400803001700020006060000280000419005000080079</textarea>
            </div>
            
            <div class="controls">
                <div class="control-group" style="display:none;">
                    <label for="step-delay">步骤延迟(ms):</label>
                    <input type="number" id="step-delay" min="0" max="5000" value="0">
                </div>
                
                <button id="solve-btn">解析数独</button>
                <button id="reset-btn" class="secondary">重置</button>
            </div>
            
            <div id="message" class="message"></div>
        </div>
        
        <div class="stats">
            <div class="stat-card">
                <div class="stat-title">题目数</div>
                <div id="initial-count" class="stat-value">0</div>
            </div>
            <div class="stat-card">
                <div class="stat-title">已解数</div>
                <div id="solved-count" class="stat-value">0</div>
            </div>
            <div class="stat-card">
                <div class="stat-title">待解数</div>
                <div id="remaining-count" class="stat-value">0</div>
            </div>
            <div class="stat-card">
                <div class="stat-title">耗时</div>
                <div id="time-elapsed" class="stat-value time-value">0ms</div>
            </div>
            <div class="stat-card">
                <div class="stat-title">解数</div>
                <div id="time-jieda" class="stat-value">...</div>
            </div>
        </div>
        
        <div id="status" class="status"></div>
    </div>
    
    <div id="solutions-container" class="container solutions-container" style="display: none;">
        <h2>解题结果</h2>
        <div id="solutions-list" class="sudoku-grids"></div>
    </div>
    
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const solveBtn = document.getElementById('solve-btn');
            const resetBtn = document.getElementById('reset-btn');
            const sudokuInput = document.getElementById('sudoku-input');
            const messageEl = document.getElementById('message');
            const statusEl = document.getElementById('status');
            const stepDelayInput = document.getElementById('step-delay');
            const initialCountEl = document.getElementById('initial-count');
            const solvedCountEl = document.getElementById('solved-count');
            const remainingCountEl = document.getElementById('remaining-count');
            const timeElapsedEl = document.getElementById('time-elapsed');
            const solutionsContainer = document.getElementById('solutions-container');
            const solutionsList = document.getElementById('solutions-list');            
            const timejieda = document.getElementById('time-jieda');
            let sdo = [];
            let solving = false;
            let startTime = 0;
            let timer = null;
            let solutions = [];
            
            solveBtn.addEventListener('click', startSolving);
            resetBtn.addEventListener('click', reset);
            
            function reset() {
                clearInterval(timer);
                solving = false;
                sdo = [];
                solutions = [];
                solutionsList.innerHTML = '';
                solutionsContainer.style.display = 'none';
                statusEl.textContent = '';
                statusEl.className = 'status';
                messageEl.textContent = '';
                messageEl.className = 'message';
                initialCountEl.textContent = '0';
                solvedCountEl.textContent = '0';
                remainingCountEl.textContent = '0';
                timeElapsedEl.textContent = '0ms';
            }
            
            function startSolving() {
                if (solving) return;
                
                const input = sudokuInput.value.trim();
                
                // 验证输入
                if (input.length !== 81) {
                    showMessage('请输入81位数字(0表示空格)', 'error');
                    return;
                }
                
                if (!/^[0-9]+$/.test(input)) {
                    showMessage('输入只能包含数字', 'error');
                    return;
                }
                
                // 转换为二维数组
                sdo = [];
                for (let x = 0; x < 9; x++) {
                    sdo[x] = [];
                    for (let y = 0; y < 9; y++) {
                        const index = y * 9 + x;
                        const value = parseInt(input.charAt(index));
                        
                        if (value < 0 || value > 9) {
                            showMessage(`位置(${x+1},${y+1})的值${value}无效(必须是0-9)`, 'error');
                            return;
                        }
                        
                        sdo[x][y] = {
                            v: value === 0 ? [] : value,
                            original: value !== 0,
                            enumerated: false
                        };
                    }
                }
                
                // 验证初始数独是否有效
                if (!validateSudoku(sdo)) {
                    showMessage('初始数独有冲突，请检查输入', 'error');
                    return;
                }
                
                showMessage('数独验证通过，开始解题...', 'success');
                
                // 初始化可能的值
                initializePossibleValues(sdo);
                
                // 显示初始状态
                displayInitialSudoku(sdo);
                updateStats();
                
                // 开始解题
                solving = true;
                solutions = [];
                startTime = performance.now();
                updateTimer();
                timer = setInterval(updateTimer, 100);
                
                // 开始解题过程
                setTimeout(() => solveWithAllSolutions(sdo), 0);
            }
            
            function updateTimer() {
                const elapsed = performance.now() - startTime;
                timeElapsedEl.textContent = `${Math.round(elapsed)}ms`;
            }
            
            function updateStats() {
                let initial = 0;
                let solved = 0;
                let remaining = 0;
                
                for (let x = 0; x < 9; x++) {
                    for (let y = 0; y < 9; y++) {
                        if (sdo[x][y].original) {
                            initial++;
                        } else if (!Array.isArray(sdo[x][y].v)) {
                            solved++;
                        } else {
                            remaining++;
                        }
                    }
                }
                
                initialCountEl.textContent = initial;
                solvedCountEl.textContent = solved;
                remainingCountEl.textContent = remaining;
            }
            
            function displayInitialSudoku(sdo) {
                solutionsList.innerHTML = '';
                const wrapper = document.createElement('div');
                wrapper.className = 'sudoku-wrapper';
                
                const title = document.createElement('h3');
                title.className = 'solution-title';
                title.textContent = '初始数独';
                wrapper.appendChild(title);
                
                const grid = document.createElement('div');
                grid.className = 'sudoku-grid';
                
                for (let y = 0; y < 9; y++) {
                    for (let x = 0; x < 9; x++) {
                        const cell = document.createElement('div');
                        cell.className = 'cell';
                        
                        // 添加粗边框
                        if (x === 2 || x === 5) cell.classList.add('thick-border-right');
                        if (y === 2 || y === 5) cell.classList.add('thick-border-bottom');
                        
                        if (sdo[x][y].original) {
                            cell.classList.add('original');
                            cell.textContent = sdo[x][y].v;
                        } else if (!Array.isArray(sdo[x][y].v)) {
                            if (sdo[x][y].enumerated) {
                                cell.classList.add('enumerated');
                            } else {
                                cell.classList.add('solved');
                            }
                            cell.textContent = sdo[x][y].v;
                        } else {
                            // 显示候选数量
                            const count = document.createElement('span');
                            count.className = 'cell-count';
                            count.textContent = sdo[x][y].v.length;
                            cell.appendChild(count);
                            
                            // 添加悬停提示
                            const hint = document.createElement('div');
                            hint.className = 'cell-hint';
                            hint.textContent = `候选数字: ${sdo[x][y].v.join(', ')}`;
                            cell.appendChild(hint);
                        }
                        
                        grid.appendChild(cell);
                    }
                }
                
                wrapper.appendChild(grid);
                solutionsList.appendChild(wrapper);
                solutionsContainer.style.display = 'block';
            }
            
            async function solveWithAllSolutions(initialSdo) {
                const stepDelay = parseInt(stepDelayInput.value);
                
                try {
                    // 先尝试基本方法解题
                    let sdo = JSON.parse(JSON.stringify(initialSdo));
                    let changed;
                    
                    do {
                        changed = false;
                        
                        // 检查唯一可能的值
                        for (let x = 0; x < 9; x++) {
                            for (let y = 0; y < 9; y++) {
                                if (Array.isArray(sdo[x][y].v)) {
                                    if (sdo[x][y].v.length === 1) {
                                        // 只有一个可能的值，确定为该值
                                        const value = sdo[x][y].v[0];
                                        sdo[x][y].v = value;
                                        removeValueFromPeers(sdo, x, y, value);
                                        changed = true;
                                        
                                        // 更新显示
                                        updateStats();
                                        console.log(`基本排除法: 确定(${x+1},${y+1})为${value}`);
                                        
                                        // 延迟以便观察
                                        if (stepDelay > 0) {
                                            await new Promise(resolve => setTimeout(resolve, stepDelay));
                                        }
                                    } else if (sdo[x][y].v.length === 0) {
                                        throw new Error(`无解: 位置(${x+1},${y+1})没有候选数字`);
                                    }
                                }
                            }
                        }
                        
                        if (!changed) {
                            // 检查隐藏单一候选
                            changed = await checkHiddenSingles(sdo, stepDelay);
                        }
                        
                    } while (changed);
                    
                    // 检查是否已解决
                    const remaining = countRemainingCells(sdo);
                    if (remaining === 0) {
                        // 验证解是否正确
                        if (validateSolution(sdo)) {
                            // 找到一个解
                            solutions.push(JSON.parse(JSON.stringify(sdo)));
                            finishSolving();
                            return;
                        } else {
                            throw new Error('找到的解无效');
                        }
                    }
                    
                    // 第二步：尝试枚举法寻找所有解
                    statusEl.innerHTML = '<span class="loading"></span>正在寻找所有可能的解...';
                    statusEl.className = 'status progress';
                    
                    // 寻找所有解
                    await findAllSolutions(sdo, stepDelay);
                    
                    if (solutions.length === 0) {
                        throw new Error('无法找到有效解');
                    }
                    
                    finishSolving();
                    
                } catch (error) {
                    showMessage(error.message, 'error');
                    console.error(error);
                    finishSolving(false);
                }
            }
            
            async function findAllSolutions(sdo, stepDelay) {
                // 找到候选最少的单元格
                let minCandidates = 10;
                let targetX = -1;
                let targetY = -1;
                
                for (let x = 0; x < 9; x++) {
                    for (let y = 0; y < 9; y++) {
                        if (Array.isArray(sdo[x][y].v) && sdo[x][y].v.length > 0 && sdo[x][y].v.length < minCandidates) {
                            minCandidates = sdo[x][y].v.length;
                            targetX = x;
                            targetY = y;
                        }
                    }
                }
                
                if (targetX === -1) {
                    // 验证解是否正确
                    if (validateSolution(sdo)) {
                        // 找到一个解
                        solutions.push(JSON.parse(JSON.stringify(sdo)));
                    }
                    return;
                }
                
                // 尝试每个候选值
                const candidates = [...sdo[targetX][targetY].v];
                console.log(`枚举法: 尝试位置(${targetX+1},${targetY+1})的候选值 ${candidates.join(',')}`);
                
                for (const value of candidates) {
                    // 创建副本
                    const sdoCopy = JSON.parse(JSON.stringify(sdo));
                    sdoCopy[targetX][targetY].v = value;
                    sdoCopy[targetX][targetY].enumerated = true;
                    removeValueFromPeers(sdoCopy, targetX, targetY, value);
                    
                    // 更新显示
                    updateStats();
                    console.log(`尝试: 位置(${targetX+1},${targetY+1})设为${value}`);
                    
                    if (stepDelay > 0) {
                        await new Promise(resolve => setTimeout(resolve, stepDelay));
                    }
                    
                    // 递归尝试解决
                    await findAllSolutions(sdoCopy, stepDelay);
                    
                    // 如果已经找到足够多的解，可以提前终止
                    if (solutions.length >= 10) {
                        break;
                    }
                }
            }
            
            function validateSolution(sdo) {
                // 检查所有单元格是否已填
                for (let x = 0; x < 9; x++) {
                    for (let y = 0; y < 9; y++) {
                        if (Array.isArray(sdo[x][y].v)) {
                            return false;
                        }
                    }
                }
                
                // 检查行
                for (let y = 0; y < 9; y++) {
                    const row = [];
                    for (let x = 0; x < 9; x++) {
                        row.push(sdo[x][y].v);
                    }
                    if (new Set(row).size !== 9) return false;
                }
                
                // 检查列
                for (let x = 0; x < 9; x++) {
                    const col = [];
                    for (let y = 0; y < 9; y++) {
                        col.push(sdo[x][y].v);
                    }
                    if (new Set(col).size !== 9) return false;
                }
                
                // 检查3x3宫格
                for (let g = 0; g < 9; g++) {
                    const grid = [];
                    const startX = Math.floor(g % 3) * 3;
                    const startY = Math.floor(g / 3) * 3;
                    
                    for (let x = startX; x < startX + 3; x++) {
                        for (let y = startY; y < startY + 3; y++) {
                            grid.push(sdo[x][y].v);
                        }
                    }
                    if (new Set(grid).size !== 9) return false;
                }
                
                return true;
            }
            
            function finishSolving(success = true) {
                clearInterval(timer);
                solving = false;
                
                if (solutions.length > 0) {
                    timejieda.textContent = `${solutions.length}`;
                    statusEl.textContent = `找到 ${solutions.length} 个解`;
                    statusEl.className = 'status completed';
                    
                    // 显示所有解
                    displayAllSolutions();
                } else {
                    statusEl.textContent = '解题完成，但未找到有效解';
                    statusEl.className = 'status progress';
                }
                
                updateStats();
            }
            
            function displayAllSolutions() {
                solutionsList.innerHTML = '';
                
                solutions.forEach((solution, index) => {
                    const wrapper = document.createElement('div');
                    wrapper.className = 'sudoku-wrapper';
                    
                    const title = document.createElement('h3');
                    title.className = 'solution-title';
                    
                    const titleText = document.createElement('span');
                    titleText.textContent = `解 ${index + 1}`;
                    title.appendChild(titleText);
                    
                    // 添加复制按钮
                    const copyBtn = document.createElement('button');
                    copyBtn.className = 'copy-btn';
                    copyBtn.textContent = '复制答案';
                    copyBtn.onclick = () => copySolution(solution, copyBtn);
                    title.appendChild(copyBtn);
                    
                    wrapper.appendChild(title);
                    
                    const grid = document.createElement('div');
                    grid.className = 'sudoku-grid';
                    
                    for (let y = 0; y < 9; y++) {
                        for (let x = 0; x < 9; x++) {
                            const cell = document.createElement('div');
                            cell.className = 'cell';
                            
                            // 添加粗边框
                            if (x === 2 || x === 5) cell.classList.add('thick-border-right');
                            if (y === 2 || y === 5) cell.classList.add('thick-border-bottom');
                            
                            if (solution[x][y].original) {
                                cell.classList.add('original');
                                cell.textContent = solution[x][y].v;
                            } else if (!Array.isArray(solution[x][y].v)) {
                                if (solution[x][y].enumerated) {
                                    cell.classList.add('enumerated');
                                } else {
                                    cell.classList.add('solved');
                                }
                                cell.textContent = solution[x][y].v;
                            }
                            
                            grid.appendChild(cell);
                        }
                    }
                    
                    wrapper.appendChild(grid);
                    solutionsList.appendChild(wrapper);
                });
                
                solutionsContainer.style.display = 'block';
            }
            
function copySolution(solution, btn) {
    // 生成81位数字字符串
    let solutionStr = '';
    for (let y = 0; y < 9; y++) {
        for (let x = 0; x < 9; x++) {
            solutionStr += solution[x][y].v;
        }
    }

    // 方法1: 使用现代Clipboard API
    if (navigator.clipboard) {
        navigator.clipboard.writeText(solutionStr).then(() => {
            showCopySuccess(btn);
        }).catch(() => {
            // 如果Clipboard API失败，尝试备用方法
            useFallbackCopyMethod(solutionStr, btn);
        });
    } else {
        // 如果不支持Clipboard API，使用备用方法
        useFallbackCopyMethod(solutionStr, btn);
    }
}

function useFallbackCopyMethod(text, btn) {
    // 方法2: 使用document.execCommand作为备用方案
    const textarea = document.createElement('textarea');
    textarea.value = text;
    textarea.style.position = 'fixed';  // 防止页面滚动
    document.body.appendChild(textarea);
    textarea.select();
    
    try {
        const successful = document.execCommand('copy');
        if (successful) {
            showCopySuccess(btn);
        } else {
            showCopyFailure(btn);
        }
    } catch (err) {
        showCopyFailure(btn);
    } finally {
        document.body.removeChild(textarea);
    }
}

function showCopySuccess(btn) {
    btn.textContent = '已复制!';
    btn.classList.add('copied');
    setTimeout(() => {
        btn.textContent = '复制答案';
        btn.classList.remove('copied');
    }, 2000);
}

function showCopyFailure(btn) {
    const originalText = btn.textContent;
    btn.textContent = '复制失败';
    btn.classList.add('error');
    setTimeout(() => {
        btn.textContent = originalText;
        btn.classList.remove('error');
    }, 2000);
    
    // 提示用户手动复制
    alert('自动复制失败，请手动选择并复制文本');
}
            
            function validateSudoku(sdo) {
                // 检查行
                for (let y = 0; y < 9; y++) {
                    const row = gx(sdo, y);
                    if (hasDuplicates(row)) return false;
                }
                
                // 检查列
                for (let x = 0; x < 9; x++) {
                    const col = gy(sdo, x);
                    if (hasDuplicates(col)) return false;
                }
                
                // 检查3x3宫格
                for (let g = 0; g < 9; g++) {
                    const grid = gg(sdo, g);
                    if (hasDuplicates(grid)) return false;
                }
                
                return true;
            }
            
            function hasDuplicates(arr) {
                const seen = {};
                for (const num of arr) {
                    if (num !== 0 && seen[num]) return true;
                    seen[num] = true;
                }
                return false;
            }
            
            function initializePossibleValues(sdo) {
                for (let x = 0; x < 9; x++) {
                    for (let y = 0; y < 9; y++) {
                        if (sdo[x][y].v === 0 || Array.isArray(sdo[x][y].v)) {
                            // 获取行、列、宫格中已有的数字
                            const row = gx(sdo, y);
                            const col = gy(sdo, x);
                            const grid = gg(sdo, Math.floor(x / 3) + Math.floor(y / 3) * 3);
                            
                            const existing = [...new Set([...row, ...col, ...grid])].filter(n => n !== 0);
                            const possible = Array.from({length: 9}, (_, i) => i + 1)
                                .filter(n => !existing.includes(n));
                            
                            sdo[x][y].v = possible;
                        }
                    }
                }
            }
            
            async function checkHiddenSingles(sdo, stepDelay) {
                let changed = false;
                
                // 检查行中的隐藏单一候选
                for (let y = 0; y < 9; y++) {
                    const candidates = {};
                    
                    // 收集行中所有候选数字
                    for (let x = 0; x < 9; x++) {
                        if (Array.isArray(sdo[x][y].v)) {
                            sdo[x][y].v.forEach(num => {
                                if (!candidates[num]) candidates[num] = [];
                                candidates[num].push({x, y});
                            });
                        }
                    }
                    
                    // 检查是否有数字只出现在一个单元格中
                    for (const num in candidates) {
                        if (candidates[num].length === 1) {
                            const {x, y} = candidates[num][0];
                            if (sdo[x][y].v.length > 1) {
                                sdo[x][y].v = parseInt(num);
                                removeValueFromPeers(sdo, x, y, parseInt(num));
                                changed = true;
                                
                                // 更新显示
                                updateStats();
                                console.log(`隐藏单一候选(行): 确定(${x+1},${y+1})为${num}`);
                                
                                // 延迟以便观察
                                if (stepDelay > 0) {
                                    await new Promise(resolve => setTimeout(resolve, stepDelay));
                                }
                            }
                        }
                    }
                }
                
                // 检查列中的隐藏单一候选
                for (let x = 0; x < 9; x++) {
                    const candidates = {};
                    
                    // 收集列中所有候选数字
                    for (let y = 0; y < 9; y++) {
                        if (Array.isArray(sdo[x][y].v)) {
                            sdo[x][y].v.forEach(num => {
                                if (!candidates[num]) candidates[num] = [];
                                candidates[num].push({x, y});
                            });
                        }
                    }
                    
                    // 检查是否有数字只出现在一个单元格中
                    for (const num in candidates) {
                        if (candidates[num].length === 1) {
                            const {x, y} = candidates[num][0];
                            if (sdo[x][y].v.length > 1) {
                                sdo[x][y].v = parseInt(num);
                                removeValueFromPeers(sdo, x, y, parseInt(num));
                                changed = true;
                                
                                // 更新显示
                                updateStats();
                                console.log(`隐藏单一候选(列): 确定(${x+1},${y+1})为${num}`);
                                
                                // 延迟以便观察
                                if (stepDelay > 0) {
                                    await new Promise(resolve => setTimeout(resolve, stepDelay));
                                }
                            }
                        }
                    }
                }
                
                // 检查宫格中的隐藏单一候选
                for (let g = 0; g < 9; g++) {
                    const candidates = {};
                    const startX = Math.floor(g % 3) * 3;
                    const startY = Math.floor(g / 3) * 3;
                    
                    // 收集宫格中所有候选数字
                    for (let x = startX; x < startX + 3; x++) {
                        for (let y = startY; y < startY + 3; y++) {
                            if (Array.isArray(sdo[x][y].v)) {
                                sdo[x][y].v.forEach(num => {
                                    if (!candidates[num]) candidates[num] = [];
                                    candidates[num].push({x, y});
                                });
                            }
                        }
                    }
                    
                    // 检查是否有数字只出现在一个单元格中
                    for (const num in candidates) {
                        if (candidates[num].length === 1) {
                            const {x, y} = candidates[num][0];
                            if (sdo[x][y].v.length > 1) {
                                sdo[x][y].v = parseInt(num);
                                removeValueFromPeers(sdo, x, y, parseInt(num));
                                changed = true;
                                
                                // 更新显示
                                updateStats();
                                console.log(`隐藏单一候选(宫格): 确定(${x+1},${y+1})为${num}`);
                                
                                // 延迟以便观察
                                if (stepDelay > 0) {
                                    await new Promise(resolve => setTimeout(resolve, stepDelay));
                                }
                            }
                        }
                    }
                }
                
                return changed;
            }
            
            function removeValueFromPeers(sdo, x, y, value) {
                // 从行中移除
                for (let i = 0; i < 9; i++) {
                    if (Array.isArray(sdo[i][y].v)) {
                        const index = sdo[i][y].v.indexOf(value);
                        if (index !== -1) {
                            sdo[i][y].v.splice(index, 1);
                        }
                    }
                }
                
                // 从列中移除
                for (let j = 0; j < 9; j++) {
                    if (Array.isArray(sdo[x][j].v)) {
                        const index = sdo[x][j].v.indexOf(value);
                        if (index !== -1) {
                            sdo[x][j].v.splice(index, 1);
                        }
                    }
                }
                
                // 从3x3宫格中移除
                const gridX = Math.floor(x / 3) * 3;
                const gridY = Math.floor(y / 3) * 3;
                for (let i = gridX; i < gridX + 3; i++) {
                    for (let j = gridY; j < gridY + 3; j++) {
                        if (Array.isArray(sdo[i][j].v)) {
                            const index = sdo[i][j].v.indexOf(value);
                            if (index !== -1) {
                                sdo[i][j].v.splice(index, 1);
                            }
                        }
                    }
                }
            }
            
            function countRemainingCells(sdo) {
                let count = 0;
                for (let x = 0; x < 9; x++) {
                    for (let y = 0; y < 9; y++) {
                        if (Array.isArray(sdo[x][y].v)) {
                            count++;
                        }
                    }
                }
                return count;
            }
            
            // 获取行数字
            function gx(sdo, y) {
                const row = [];
                for (let x = 0; x < 9; x++) {
                    if (!Array.isArray(sdo[x][y].v)) {
                        row.push(sdo[x][y].v);
                    }
                }
                return row;
            }
            
            // 获取列数字
            function gy(sdo, x) {
                const col = [];
                for (let y = 0; y < 9; y++) {
                    if (!Array.isArray(sdo[x][y].v)) {
                        col.push(sdo[x][y].v);
                    }
                }
                return col;
            }
            
            // 获取3x3宫格数字
            function gg(sdo, g) {
                const grid = [];
                const startX = Math.floor(g % 3) * 3;
                const startY = Math.floor(g / 3) * 3;
                
                for (let x = startX; x < startX + 3; x++) {
                    for (let y = startY; y < startY + 3; y++) {
                        if (!Array.isArray(sdo[x][y].v)) {
                            grid.push(sdo[x][y].v);
                        }
                    }
                }
                
                return grid;
            }
            
            function showMessage(msg, type) {
                messageEl.textContent = msg;
                messageEl.className = 'message ' + type;
            }
        });
    </script>
</body>
</html>